package cn.song.docflow.util;

import cn.song.docflow.model.dto.ImageParamDTO;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.data.PictureRenderData;
import com.deepoove.poi.data.Pictures;
import cn.song.docflow.common.CustomException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.*;

/**
 * 文档处理工具类
 *
 * @author SongRenShuo
 * @date 2023/05/20
 */
@Slf4j
public class DocumentUtil {

    /**
     * 线程池核心线程数
     */
    private static final int CORE_POOL_SIZE = 1;

    /**
     * 线程池最大线程数
     */
    private static final int MAX_POOL_SIZE = 5;

    /**
     * 空闲线程存活时间(秒)
     */
    private static final long KEEP_ALIVE_TIME = 60L;

    /**
     * 任务队列容量
     */
    private static final int QUEUE_CAPACITY = 100;

    /**
     * LibreOffice转换超时时间(秒)
     */
    private static final long CONVERSION_TIMEOUT = 30L;

    /**
     * CompletableFuture等待时间(秒)
     */
    private static final long FUTURE_TIMEOUT = 60L;

    /**
     * 数据类型枚举
     */
    public enum DataType {
        /** 文本类型 */
        TEXT,
        /** 图片类型 */
        IMAGE
    }

    /** 转换队列 */
    private static final ExecutorService CONVERSION_EXECUTOR = new ThreadPoolExecutor(
            CORE_POOL_SIZE,
            MAX_POOL_SIZE,
            KEEP_ALIVE_TIME,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(QUEUE_CAPACITY),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    /**
     * 生成Word文档
     *
     * @param templatePath 模板路径
     * @param dataMap      数据映射
     * @param outputDir    输出目录
     * @return 生成的Word文档路径
     * @throws IOException IO异常
     */
    public static String generateWord(String templatePath, Map<String, Object> dataMap, String outputDir) throws IOException {
        // 验证文件路径
        validateFilePath(templatePath);
        validateFilePath(outputDir);

        // 创建输出目录
        Path outputPath = Paths.get(outputDir);
        if (!Files.exists(outputPath)) {
            Files.createDirectories(outputPath);
        }

        // 生成文件名 - 使用documentName或默认命名
        String fileName = generateFileName(dataMap, ".docx");
        String outputFilePath = outputPath.resolve(fileName).toString();

        // 使用POI-TL渲染模板
        Configure config = Configure.builder().build();
        try (XWPFTemplate template = XWPFTemplate.compile(templatePath, config).render(dataMap)) {
            template.writeAndClose(Files.newOutputStream(Paths.get(outputFilePath)));
        }

        return outputFilePath;
    }

    /**
     * 生成Word文档并转换为PDF
     *
     * @param templatePath 模板路径
     * @param dataMap      数据映射
     * @param outputDir    输出目录
     * @return 生成的PDF文档路径
     * @throws Exception 异常
     */
    public static String generateWordAndPdf(String templatePath, Map<String, Object> dataMap, String outputDir) throws Exception {
        // 生成Word文档
        String wordFilePath = generateWord(templatePath, dataMap, outputDir);

        // 转换为PDF
        String pdfFileName = generateFileName(dataMap, ".pdf");
        String pdfFilePath = Paths.get(outputDir).resolve(pdfFileName).toString();
        convertToPdfUsingLibreOffice(wordFilePath, pdfFilePath);

        return pdfFilePath;
    }

    /**
     * 使用队列机制生成Word文档并转换为PDF
     * <p>该方法使用单线程队列顺序处理PDF转换任务，避免并发执行LibreOffice导致的问题</p>
     *
     * @param templatePath 模板文件路径
     * @param dataMap      数据映射
     * @param outputDir    输出目录
     * @return 生成的PDF文档路径
     * @throws Exception 异常
     */
    public static String generateWordAndPdfQueue(String templatePath, Map<String, Object> dataMap, String outputDir) throws Exception {
        // 使用队列机制处理PDF转换
        try {
            CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
                try {
                    // 生成Word文档
                    String wordFilePath = generateWord(templatePath, dataMap, outputDir);

                    // 转换为PDF
                    String pdfFileName = generateFileName(dataMap, ".pdf");
                    String pdfFilePath = Paths.get(outputDir).resolve(pdfFileName).toString();
                    convertToPdfUsingLibreOffice(wordFilePath, pdfFilePath);
                    log.info("PDF生成成功: {}", pdfFilePath);
                    return pdfFilePath;
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }, CONVERSION_EXECUTOR);

            // 等待并返回结果
            return future.get(FUTURE_TIMEOUT, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("PDF转换失败: {}", e.getMessage(), e);
            throw new IOException("PDF转换失败", e);
        }
    }


    /**
     * 将Word文档转换为PDF
     *
     * @param wordFilePath Word文档路径
     * @return PDF文档路径
     */
    public static String convertWordToPdf(String wordFilePath) {
        try {
            // 验证文件路径
            validateFilePath(wordFilePath);

            // 检查文件是否存在
            File wordFile = new File(wordFilePath);
            if (!wordFile.exists()) {
                throw new CustomException("Word文件不存在: " + wordFilePath);
            }

            // 生成PDF文件路径
            String pdfFilePath = FilenameUtils.removeExtension(wordFilePath) + ".pdf";

            // 使用LibreOffice转换
            convertToPdfUsingLibreOffice(wordFilePath, pdfFilePath);

            return pdfFilePath;
        } catch (Exception e) {
            log.error("Word转PDF失败: {}", e.getMessage());
            throw new CustomException("Word转PDF失败: " + e.getMessage());
        }
    }

    /**
     * 使用LibreOffice将Word转换为PDF
     *
     * @param inputFile  输入文件路径
     * @param outputFile 输出文件路径
     * @throws Exception 异常
     */
    private static void convertToPdfUsingLibreOffice(String inputFile, String outputFile) throws Exception {
        // 验证文件路径
        validateFilePath(inputFile);
        validateFilePath(outputFile);

        // 创建输出目录
        File outputFileObj = new File(outputFile);
        File parentDir = outputFileObj.getParentFile();
        if (!parentDir.exists()) {
            parentDir.mkdirs();
        }

        // 构建LibreOffice命令
        String[] command = {
                "cmd", "/C",
                "soffice",
                "--headless",
                "--convert-to",
                "pdf",
                "--outdir",
                parentDir.getAbsolutePath(),
                inputFile
        };

        // 执行命令
        Process process = Runtime.getRuntime().exec(command);

        // 设置超时
        boolean completed = process.waitFor(CONVERSION_TIMEOUT, TimeUnit.SECONDS);
        if (!completed) {
            process.destroyForcibly();
            throw new CustomException("PDF转换超时");
        }

        // 检查转换结果
        int exitCode = process.exitValue();
        if (exitCode != 0) {
            throw new CustomException("PDF转换失败，退出码: " + exitCode);
        }

        // 重命名输出文件（如果需要）
        File expectedOutput = new File(outputFile);
        if (!expectedOutput.exists()) {
            File inputFileObj = new File(inputFile);
            String baseName = FilenameUtils.getBaseName(inputFileObj.getName());
            File generatedPdf = new File(parentDir, baseName + ".pdf");
            if (generatedPdf.exists()) {
                generatedPdf.renameTo(expectedOutput);
            } else {
                throw new CustomException("PDF文件未生成");
            }
        }
    }


    /**
     * 生成文件名
     *
     * @param dataMap    数据映射，从中获取documentName
     * @param extension 文件扩展名
     * @return 文件名
     */
    private static String generateFileName(Map<String, Object> dataMap, String extension) {
        // 优先使用documentName作为文件名
        if (dataMap != null && dataMap.containsKey("documentName")) {
            String documentName = dataMap.get("documentName").toString();
            // 去除文件名中的非法字符
            documentName = documentName.replaceAll("[/:*?\"<>|]", "_");
            return documentName + extension;
        }

        // 如果没有documentName，则使用默认命名规则
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        String uuid = UUID.randomUUID().toString().substring(0, 8);
        return "doc_" + timestamp + "_" + uuid + extension;
    }

    /**
     * 生成文件名（兼容旧版本）
     *
     * @param extension 文件扩展名
     * @return 文件名
     */
    private static String generateFileName(String extension) {
        return generateFileName(null, extension);
    }

    /**
     * 验证文件路径
     *
     * @param filePath 文件路径
     */
    private static void validateFilePath(String filePath) {
        if (!StringUtils.hasText(filePath)) {
            throw new CustomException("文件路径不能为空");
        }

        // 检查命令注入字符
        String[] dangerousChars = {"|", "&", ";", "$", "`", ">", "<", "*", "?"};
        for (String c : dangerousChars) {
            if (filePath.contains(c)) {
                throw new CustomException("文件路径包含非法字符: " + c);
            }
        }

        // 从根本上避免路径遍历攻击 - 使用纯字符串操作，不使用Path对象处理用户输入
        // 检查是否包含路径遍历序列
        if (filePath.contains("../") || filePath.contains("..\\") || filePath.contains("./../") || filePath.contains(".\\..\\")) {
            throw new CustomException("文件路径包含路径遍历攻击");
        }

        // 检查是否以/../或\..\开头（绝对路径的路径遍历）
        if (filePath.startsWith("/../") || filePath.startsWith("\\..\\")) {
            throw new CustomException("文件路径包含路径遍历攻击");
        }
    }

    /**
     * 安全地处理本地图片路径
     * 确保路径在uploads目录下，避免路径遍历攻击
     *
     * @param imagePath 图片路径
     * @return 安全的图片路径
     */
    private static String safeLocalImagePath(String imagePath) {
        validateFilePath(imagePath);

        // 检查是否是网络图片，如果是直接返回
        if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
            return imagePath;
        }

        // 确保是相对路径，避免绝对路径访问
        if (imagePath.startsWith("/") || imagePath.startsWith("\\") ||
            (imagePath.length() >= 2 && Character.isLetter(imagePath.charAt(0)) && imagePath.charAt(1) == ':')) {
            throw new CustomException("不允许使用绝对路径访问本地图片");
        }

        // 确保路径在uploads目录下（根据应用逻辑）
        // 注意：实际应用中应根据配置的uploadDir进行验证
        return imagePath;
    }

    /**
     * 数据信息类
     */
    @Data
    public static class DataInfo {
        /**
         * 数据类型
         */
        private DataType type;

        /**
         * 数据值
         */
        private Object value;

        /**
         * 构造函数
         *
         * @param type  数据类型
         * @param value 数据值
         */
        public DataInfo(DataType type, Object value) {
            this.type = type;
            this.value = value;
        }

        /**
         * 创建文本数据
         *
         * @param text 文本
         * @return 数据信息
         */
        public static DataInfo text(String text) {
            return new DataInfo(DataType.TEXT, text);
        }

        /**
         * 创建图片数据
         *
         * @param imagePath 图片路径
         * @return 数据信息
         */
        public static DataInfo image(String imagePath) {
            return new DataInfo(DataType.IMAGE, new ImageData(imagePath, 100, 100));
        }

        /**
         * 创建图片数据（指定尺寸）
         *
         * @param imagePath 图片路径
         * @param width 宽度
         * @param height 高度
         * @return 数据信息
         */
        public static DataInfo image(String imagePath, Integer width, Integer height) {
            return new DataInfo(DataType.IMAGE, new ImageData(imagePath, width, height));
        }

        /**
         * 创建图片数据（从DTO）
         *
         * @param imageParam 图片参数DTO
         * @return 数据信息
         */
        public static DataInfo image(ImageParamDTO imageParam) {
            return new DataInfo(DataType.IMAGE,
                    new ImageData(imageParam.getPath(), imageParam.getWidth(), imageParam.getHeight()));
        }

        /**
         * 获取渲染数据
         *
         * @return 渲染数据
         */
        public Object getRenderData() {
            if (type == DataType.IMAGE && value instanceof ImageData) {
                return ((ImageData) value).getPictureRenderData();
            } else if (type == DataType.IMAGE && value instanceof String) {
                try {
                    return Pictures.ofLocal((String) value).create();
                } catch (Exception e) {
                    log.error("创建图片数据失败: {}", e.getMessage());
                    return "";
                }
            }
            return value;
        }
    }

    /**
     * 图片数据类
     */
    @Data
    public static class ImageData {
        /**
         * 图片路径
         */
        private String path;

        /**
         * 宽度
         */
        private int width;

        /**
         * 高度
         */
        private int height;

        /**
         * 构造函数
         *
         * @param path   图片路径
         * @param width  宽度
         * @param height 高度
         */
        public ImageData(String path, int width, int height) {
            this.path = path;
            this.width = width;
            this.height = height;
        }

        /**
         * 从ImageParamDTO创建ImageData
         *
         * @param imageParam 图片参数DTO
         * @return ImageData实例
         */
        public static ImageData fromImageParamDTO(ImageParamDTO imageParam) {
            if (imageParam == null) {
                return null;
            }
            return new ImageData(
                    imageParam.getPath(),
                    imageParam.getWidth() != null ? imageParam.getWidth() : 100,
                    imageParam.getHeight() != null ? imageParam.getHeight() : 100
            );
        }

        /**
         * 获取图片渲染数据
         *
         * @return 图片渲染数据
         */
        public PictureRenderData getPictureRenderData() {
            try {
                if (path.startsWith("http://") || path.startsWith("https://")) {
                    // 处理网络图片
                    return Pictures.ofUrl(path).size(width, height).create();
                } else {
                    // 处理本地图片
                    return Pictures.ofLocal(path).size(width, height).create();
                }
            } catch (Exception e) {
                log.error("创建图片渲染数据失败: {}", e.getMessage());
                throw new CustomException("创建图片渲染数据失败: " + e.getMessage());
            }
        }
    }
}
